#import "CHMDocument.h"

#import "BCPreferencesController.h"

#include <stdio.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "chm_lib.h"

static int _threadExpansionCallback(struct chmFile *h, struct chmUnitInfo *ui, void *context);
static int _itemCountCallback(struct chmFile *h, struct chmUnitInfo *ui, void *context);

#pragma mark -

@interface CHMDocument (Private)

- (void) refreshUI;
- (BOOL) createFolderAt: (NSString*) path;
- (void) doThreadExpansion: (id) object;
- (int) doExpandItem: (struct chmUnitInfo*) item;

@end

#pragma mark -

@implementation CHMDocument

- (BOOL) readFromFile: (NSString*)fileName ofType: (NSString*)docType
{
    // This is for 10.3.9 to open the files properly.
    _filePath = [fileName copy];
    _file = chm_open([_filePath fileSystemRepresentation]);
    
    return (_file != nil);
}

- (BOOL) readFromURL: (NSURL*) absoluteURL ofType: (NSString*) typeName error: (NSError**) outError
{
    _filePath = [[absoluteURL path] copy];
    _file = chm_open([_filePath fileSystemRepresentation]);
    
    return (_file != nil);
}

- (IBAction) cancel: (id)sender
{
    if (_expanding)
    {
        [_expansionLock lock];
        
        _cancel = TRUE;
        
        [_progressIndicator setDoubleValue: 0.0];
        [_progressIndicator setIndeterminate: TRUE];
        [_expandButton setEnabled: TRUE];
        [_cancelButton setEnabled: FALSE];
        
        [_expansionLock unlock];
    }
}

- (IBAction) expand: (id)sender
{
    if (_expanding)
    {
        NSAssert(FALSE, @"Document is already expanding. Do not call twice.");
        return;
    }
    
    _expandRootPath = [[_filePath stringByDeletingPathExtension] retain];
    
    if ([self createFolderAt: _expandRootPath])
    {
        [_statusText setStringValue: NSLocalizedString(@"Expanding ...", 
                                                       @"Used to display a generic expanding message in the status bar")];
        [_progressIndicator setMaxValue: (double)_itemCount];
        [_progressIndicator setMinValue: 0.0];
        [_progressIndicator setDoubleValue: 0.0];
        [_progressIndicator setIndeterminate: FALSE];
        [_expandButton setEnabled: FALSE];
        [_cancelButton setEnabled: TRUE];
        
        [self refreshUI];
        
        _expanding = TRUE;
        _cancel = FALSE;
        
        [NSThread detachNewThreadSelector: @selector(doThreadExpansion:) 
                                 toTarget: self
                               withObject: nil];
    }
}

#pragma mark NSDocument Methods

- (void) close
{
    if (_expanding)
    {
        [self cancel: self];
    }
    
    [super close];
}

- (BOOL) presentError: (NSError*) error
{
    NSAssert(FALSE, @"Not implemented yet.");
    
    return FALSE;
}

- (BOOL) validateUserInterfaceItem: (id <NSValidatedUserInterfaceItem>) item
{
    BOOL valid = NO;
    
    if ([item action] == @selector(expand:))
    {
        valid = (_expanding == NO);
    }
    else if ([item action] == @selector(cancel:))
    {
        valid = (_expanding == YES);
    }
    else
    {
        return [super validateUserInterfaceItem: item];
    }
    
    return valid;
}

#pragma mark NSWindowController Methods

- (NSString *) windowNibName
{
    return @"CHMDocument";
}

- (void) windowControllerDidLoadNib: (NSWindowController*) windowController
{
    _itemCount = 0;
    
    chm_enumerate(_file, CHM_ENUMERATE_NORMAL | CHM_ENUMERATE_DIRS | CHM_ENUMERATE_FILES, 
                  _itemCountCallback, &_itemCount);
    
    [_statusText setStringValue: NSLocalizedString(@"Loaded",
                                                   @"Used to indicate that the file was opened and loaded correctly")];
    [_itemCountText setStringValue: [NSString stringWithFormat: 
        NSLocalizedString(@"%d items", @"Used to indicate how many items are inside the CHM file"),
        _itemCount]];
    
    [super windowControllerDidLoadNib: windowController];
}

#pragma mark NSObject Methods

- (id) init
{
    if (self = [super init])
    {
        _filePath = nil;
        _file = nil;
        _itemCount = 0;
        _expandRootPath = nil;
        _expansionLock = [[NSLock alloc] init];
        _expanding = FALSE;
        _cancel = FALSE;
    }
    
    return self;
}

- (void) dealloc
{
    [_filePath release];
    [_expandRootPath release];
    [_expansionLock release];
    
    if (_file)
        chm_close(_file);
    
    [super dealloc];
}

@end

#pragma mark -

@implementation CHMDocument (Private)

- (void) refreshUI
{
    [_statusText display];
    [_expandButton display];
    [_progressIndicator display];
    [_cancelButton display];
}

- (BOOL) createFolderAt: (NSString*) path;
{
    int result = NSAlertDefaultReturn;
    NSFileManager* fileManager = [NSFileManager defaultManager];
    
    if ([fileManager fileExistsAtPath: path])
    {  
        if (![[NSUserDefaults standardUserDefaults] boolForKey: @"ReplaceFolderWithoutAsking"])
        {
            result = NSRunCriticalAlertPanel(NSLocalizedString(@"Replace Folder", @"The title of the replace folder dialog"), 
                                             NSLocalizedString(@"Folder \\U201C%@\\U201D already exists. Do you wish to replace it or cancel expansion?", 
                                                               @"Used to ask the user whether to replace a folder or not"),
                                             NSLocalizedString(@"Replace", @""), 
                                             NSLocalizedString(@"Cancel", @""), 
                                             nil, path);            
        }
        
        if (NSAlertDefaultReturn == result)
        {
            [fileManager removeFileAtPath: path handler: nil];
        }
    }
    
    if (NSAlertDefaultReturn == result)
    {
        if (![fileManager createDirectoryAtPath: path attributes: nil])
        {
            NSRunCriticalAlertPanel(NSLocalizedString(@"Error", @""),
                                    NSLocalizedString(@"Couldn\\U2019t create folder \\U201C%@.\\U201D", 
                                                      @"Used in the cannot create folder dialog"),
                                    NSLocalizedString(@"Ok", @""),
                                    nil, nil, path);
            
            return FALSE;
        }
        
        return TRUE;
    }
    
    return FALSE;
}

- (void) doThreadExpansion: (id) object
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    chm_enumerate(_file, CHM_ENUMERATE_NORMAL | CHM_ENUMERATE_DIRS | CHM_ENUMERATE_FILES, 
                  _threadExpansionCallback, self);
    
    [_expansionLock lock];
    
    if (_cancel)
    {
        [_statusText setStringValue: NSLocalizedString(@"Cancelled",
                                                       @"Used to indicate that the expansion was cancelled")];
    }
    else
    {
        [_progressIndicator setDoubleValue: [_progressIndicator maxValue]];
        [_statusText setStringValue: NSLocalizedString(@"Done",
                                                       @"Used to indicate that the expansion is done")];
    }
    
    [_cancelButton setEnabled: FALSE];
    [_expandButton setEnabled: TRUE];
    [_expandButton setTitle: NSLocalizedString(@"Close", 
                                               @"Text of close button after a successful expansion")];
    [_expandButton setAction: @selector(close)];
    
    [self refreshUI];
    
    [_expansionLock unlock];
    
    [pool release];
    return;
}

- (int) doExpandItem: (struct chmUnitInfo*) item
{
    bool cancel = false;
    
    [_expansionLock lock];
    
    [_progressIndicator incrementBy: 1.0];
    [_statusText setStringValue: [NSString stringWithFormat: 
        NSLocalizedString(@"Expanding \\U201C%@.\\U201D", 
                          @"Used to display a message about a specific file being expanded"),
        [NSString stringWithUTF8String: item->path + 1]]];
    cancel = _cancel;
    [_statusText display];
    
    [_expansionLock unlock];
    
    if (cancel)
        return CHM_ENUMERATOR_FAILURE;
    
    if (item->path[0] != '/')
        return CHM_ENUMERATOR_CONTINUE;
    
    if (item->length != 0)
    {
        NSMutableData* data = [[NSMutableData alloc] init];
        
        [data setLength: item->length];
        chm_retrieve_object(_file, item, [data mutableBytes], 0, item->length);
        
        [data writeToFile: [_expandRootPath stringByAppendingPathComponent: 
            [NSString stringWithUTF8String: item->path]] atomically: NO];
        
        [data release];
    }
    else
    {
        [[NSFileManager defaultManager] createDirectoryAtPath: 
            [_expandRootPath stringByAppendingPathComponent: 
                [NSString stringWithUTF8String: item->path]] attributes: nil];
    }
    
    return CHM_ENUMERATOR_CONTINUE;
}

@end

#pragma mark -

int _threadExpansionCallback(struct chmFile *h, struct chmUnitInfo *ui, void *context)
{
    CHMDocument* document = (CHMDocument*)context;
    
    if (!context)
        return CHM_ENUMERATOR_FAILURE;
    
    return [document doExpandItem: ui];
}

int _itemCountCallback(struct chmFile *h, struct chmUnitInfo *ui, void *context)
{
    if (context)
        (*(int*)context)++;
    
    return CHM_ENUMERATOR_CONTINUE;
}
